#!/usr/bin/env python3 
import re 
import os
import sys
import json
import xmltodict
import getopt
from xml.etree.ElementTree import Element
from xml.etree.ElementTree import tostring
from xml.etree import ElementTree as ET
from xml.dom import minidom

############################################################################################
############################## End User Functionality ######################################
############################################################################################
#1. Output all(interface+neighbor) information in pretty format to stdout
#2. Output all(interface+neighbor) information in xml/json  to stdout/file


#TODO Test
#1. JSON /XML in stdout [ok]
#2. JSON /XML in file [ok]
#3. PRETTY PRINT IN STDOUT [ok] 
#4. COMMAND LINE INPUT [ok]



############################################################################################
############################## Data Definition #############################################
############################################################################################

int_list = list()
nb_list = list()
comp_list = list()
outag = 'information'
intag_list = ['interfaces','neighbors']

rdt_hw = ''
rdt_nb = ''
rdt_if = ''

help_info = "Usage: show_vpp_info \r\n" +  \
	"\t[-h][--help]\r\n"+\
	"\t[-t TYPE][--type TYPE]\r\n"+ \
	"\t[-d DEST][--destinaion DEST]support 'stdout' and file path\r\n" +\
	"\tTYPE: 'xml', 'json', otherwise default output\r\n" +\
	"\tDEST: 'stdout' and filepath, otherwise default output to stdout\r\n"+\
	"\tThe default output is pretty-print format in stdout.\r\n"+\
	"\tversion: 1.2 --fixed incompatibility problem"



#################################################################################################
############################## Interface Level Methods ##########################################
#################################################################################################



def main(argv):
	try:
		options, args = getopt.getopt(argv,"-h-t:-d:",["help","type=","destination="])
	except getopt.GetoptError:
		print("Get help: 'show_info -h'")
		sys.exit()
	
	param_t = 'normal'
	param_d = 'stdout'
	
	for option, value in options:	
		if option in ("-h","--help"):
			print(help_info)
			return
		if option in ("-t","--type"):
			param_t = value
		if option in ("-d","--destination"):
			param_d = value
							
	show_info(show_type = param_t, show_destination = param_d)	


def show_info(show_type='xml',show_destination='stdout'):
        get_info()
        if(show_type=='json'):
                show_info_json(show_destination)
        elif(show_type=='xml'):
                show_info_xml(show_destination)
        else:
                show_info_stdout_pretty()


#############################################################################################################
############################## Upper Level Integration Methods ##############################################
#############################################################################################################

def get_info():
	global rdt_hw
	global rdt_nb
	init()		   
	get_ip6_interface()
	nb = get_neighbor()
	rdt_nb = rdt_nb + get_neighbor()
	rdt_hw = rdt_hw + get_hardware_info() #Lower level
def show_neighbor_json():
        jsonArr = json.dumps(nb_list, ensure_ascii = False)
        return jsonArr

def show_info_xml(show_destination):
	xml_elem = list_list_dic_to_xml_elem(comp_list = comp_list, outag = outag, intag_list = intag_list)
	unicode_xml = prettify(xml_elem)
	if(show_destination == 'stdout'):
		print(unicode_xml)
	elif(show_destination == 0):
		pass
	else:
		with open(show_destination, 'w') as f:
			f.write(unicode_xml)	
	return unicode_xml

def show_info_json(show_destination):
	unicode_xml = show_info_xml(0)
	ob_json = xml_to_json(unicode_xml)
	if(show_destination == 'stdout'):
		print(ob_json)
	elif(show_destination == 0):
		pass
	else:
		with open(show_destination,'w') as f:
  			f.write(ob_json)
	return ob_json

def show_info_stdout_pretty():
	global rdt_hw
	global rdt_nb
	global rdt_if
	print("\r\n========================== Hardware Information ================================\r\n")
	print(rdt_hw)
	print("\r\n========================== Interface Informaiton ===============================\r\n")
	print(rdt_if)
	print("\r\n========================== Neighbor Information ================================\r\n")
	print(rdt_nb)
	

#########################################################################################################
############################## Middle Level Formatting Implmentation ####################################
#########################################################################################################

### LIST[LIST[DICTONAR]] -> XML ELEMENT
### Input: @comp_list complex dict in list in list  eg:[int_list,nb_list]
###	   @outag     outside overall tag for list  eg: information
###        @intag_list  for each inside tag for each dictionary eg:[interfaces, neighbors]
def list_list_dic_to_xml_elem(comp_list, outag, intag_list):
	elem = Element(outag) #eg: information
	for sublist, subtag in zip(comp_list,intag_list): 
		elem.append(list_dic_to_xml_elem(outag = subtag, intag = subtag[:-1], array = sublist))
	return elem

### LIST[DICTIONARY] -> XML ELEMENT
### Input : @outag outside overall tag for list 
###         @intag inside tag for each dictionary
###         @array data list 
def list_dic_to_xml_elem(outag,intag,array):
        elem = Element(outag)
        for dic in array:
                elem.append(dic_to_xml_elem(intag,dic))
        return elem

### DICTIONARY -> XML ELEMENT
### Input: @tag should add a overall xml tag ouside
###        @dic data in type dictionary
### Output: XML element type data
def dic_to_xml_elem(tag,dic):
	elem = Element(tag)
	for key, val in dic.items():
		child = Element(key)
		if str(type(val)) == "<class 'str'>":
			child.text = str(val)
			elem.append(child)
		elif str(type(val)) == "<class 'list'>":
			for item in val:
				c_child = Element(key[:-1])
				c_child.text = str(item)
				child.append(c_child)
			elem.append(child)
		else:
			print("error")
	return elem


		
#####################################################################################################
############################ Lower Level Fetch Implementation #############################################
#####################################################################################################

# Including 4 get_xxx methods to fetch information and store data in int_list and nb_list 

def init():
	comp_list.append(int_list)
	comp_list.append(nb_list)
	get_basic_int()

def get_basic_int():			
	res_file= os.popen('vppctl show int')
	contents = res_file.read()
	match = re.findall("(\S*) \s+ (\d+) \s+ (\w*)",contents)
	# Create interface classes 
	for item in match:		
		t_dict = {}
		t_dict['ifname'] = item[0]
		t_dict['ifindex'] = item[1]
		t_dict['ifstatus'] = item[2]
		int_list.append(t_dict) # name index status

# Add more packet information into interfaces

def get_hardware_info():
	res_file = os.popen('vppctl show hardware')
	contents = res_file.read()
	
	#Use regex parsing info
	regex1 = '(\S*) \s+ \d+ \s+ \w* \s+\S+\s+ Ethernet address (\S+)\s+ (.*)\s+carrier up full duplex speed (\d+) mtu (\d+)\s+'
	regex2 = ' rx queues (\d+), rx desc (\d+), tx queues (\d+), tx desc (\d+)\s+ cpu socket (\d+)\s+'
	regex3 = ' tx frames ok \s+(\d+)\s+ tx bytes ok \s+(\d+)\s+ rx frames ok \s+(\d+)\s+ rx bytes ok \s+(\d+)'
	regex4 = '[\s|\S]*? rx q0 multicast packets \s+(\d+)'
	match = re.findall(regex1+regex2+regex3, contents)

	if match:
		for item in match:
			sub_dict = {}
			sub_dict['ifname']= item[0]
			sub_dict['ethaddr'] = item[1]
			sub_dict['dev_type'] = item[2]
			sub_dict['speed'] = item[3]
			sub_dict['mtu'] = item[4]
			sub_dict['rx_queue'] = item[5]
			sub_dict['rx_desc'] = item[6]	
			sub_dict['tx_queue'] = item[7]
			sub_dict['tx_desc'] = item[8]
			sub_dict['cpu_socket'] = item[9]
			sub_dict['tx_packet_ok'] = item[10]
			sub_dict['tx_bytes_ok'] = item[11]
			sub_dict['rx_packet_ok'] =item[12]
			sub_dict['rx_bytes_ok'] = item[13]
			#sub_dict['rx_multicast_packet'] = item[14]
			for int_dict in int_list:
				if (int_dict['ifname'] == sub_dict['ifname']):
					int_dict.update(sub_dict)
	else:
		print("in get_hardware_info no match error")
	return contents
					
def get_ip6_interface():
	# Add more address information into interfaces
	content_list = list()
	global rdt_if
	for int_dict in int_list:
		res_file = os.popen('vppctl show ip6 interface ' + int_dict['ifname'])
		contents= res_file.read()
		rdt_if = rdt_if + contents

		regex1='(\S+) is admin up\s+'
		regex2='Local unicast address\S+\s+(\S*?)\s+'
		regex3='Global unicast address\S+\s+(\S*?)\s+'
		regex4='Link-local address\S+\s+(\S*?)\s+'
		regex5='Joined group address\(es\):\s+([\s\S]*?)Ad'
		#TODO TEST AREA
		match1 = re.findall(regex1+regex2+regex3+regex4+regex5, contents)
		match2 = re.findall(regex1       +regex3+regex4+regex5, contents)
		match3 = re.findall(regex1+regex2       +regex4+regex5, contents)

		if match1:
			for item in match1:
				sub_dict = {}
				sub_dict['ifname'] = item[0]
				sub_dict['local_addr'] = item[1]
				sub_dict['global_addr'] = item[2]
				sub_dict['link_local_addr'] = item[3]
				sub_dict['group_addrs'] = pretty_group_addr(item[4])
			for int_dict in int_list:
				if (int_dict['ifname'] == sub_dict['ifname']):
					int_dict.update(sub_dict)
		
		if match2:
			for item in match2:
				sub_dict = {}
				sub_dict['ifname'] = item[0]
				sub_dict['global_addr'] = item[1]
				sub_dict['link_local_addr'] = item[2]
				sub_dict['group_addrs'] = pretty_group_addr(item[3])
				
			for int_dict in int_list:
				if (int_dict['ifname'] == sub_dict['ifname']):
					int_dict.update(sub_dict)
		if match3: 
                        for item in match3:
                                sub_dict = {}
                                sub_dict['ifname'] = item[0]
                                sub_dict['local_addr'] = item[1]
                                sub_dict['link_local_addr'] = item[2]
                                sub_dict['group_addrs'] = pretty_group_addr(item[3])
                        for int_dict in int_list:
                                if (int_dict['ifname'] == sub_dict['ifname']):
                                        int_dict.update(sub_dict)

	final_content = ''
	return final_content.join(content_list)			

def get_neighbor():
	res_file = os.popen('vppctl show ip6 neighbor')
	contents = res_file.read()	
	regex = '(\d+.\d+)(\S+)\s+(\w)\s+(\S+)\s+(\S+)'
	match = re.findall(regex, contents)
	if match:
		for item in match:
			sub_dict = {}
			sub_dict['time'] = item[0]	
			sub_dict['link_local_addr'] = item[1]
			sub_dict['flags'] = item[2]
			sub_dict['link_layer'] = item[3]
			sub_dict['interface'] = item[4]
			nb_list.append(sub_dict)
	else:
		print("in get neighbor not match error")
	return contents

##############################################################################################
############################### Library Functions ############################################
##############################################################################################

### Function: xml-->json
### Input: @xml_str: xml string
### Output: @json_str: json string
def xml_to_json(xml_str):
    # parse is xml analyser
    xml_parse = xmltodict.parse(xml_str)
    # json library dumps() method convert dict into json,loads() method convert json to dict
    # dumps() method ident=1,format json
    json_str = json.dumps(xml_parse, indent=1)
    return json_str

### Function: json-->xml
### Input: @json_str json string
### Output: @xml_str xml string
def json_to_xml(json_str):
    # xmltodict library has unparse() method. json->xml
    # parameter pretty to format xml
    xml_str = xmltodict.unparse(json_str, pretty=1)
    return xml_str

### Function: Convert array containing dicts into XML element
### Input : @outag outside overall tag for list
### 	    @intag inside tag for each dictionary
### 	    @array data list 
def dic_arr_to_xml_elem(outag,intag,array):
	elem = Element(outag)
	for dic in array:
		elem.append(dic_to_xml_elem(intag,dic))
	return elem


### Function: prettify xml element
### Input: XML element
### Output: pretty format in <type 'unicode'>
def prettify(elem):
	rough_str = tostring(elem,'utf-8')
	reparsed = minidom.parseString(rough_str)
	result = reparsed.toprettyxml(indent="	   ")
	return result

### Function: solve  unhandled group_addr issue in regex match
### Input: raw group addr string
### Output: pretty format with a list containing several group addrs 
def pretty_group_addr(string):
	gaddr_list = string.split('\n\t\t')
	new_list = list()
	for items in gaddr_list:
		new_list.append(items.strip('\n\t'))
	return new_list


###################### END ###############################################################
###################### TEST CODE #########################################################
if __name__ == '__main__':
	main(sys.argv[1:])
